#include "stdafx.h"
#include "Config.h"
#include "Values/Value.h"
#include "Values/StrValue.h"
#include "Values/Int32Value.h"
#include "CfgUtil.h"

using namespace pb::cfg;

namespace {

    const int          kSizeLength = 2;

    const int kSizeStamp   = 6; 
    const int kSizeType    = 1;
    const int kSizeVersion = 1;
    const int kSizeTail    = 24;

    const char kStamp[kSizeStamp] = { 'P', 'A', 'T', 'I', 'Y', 'A' };
    const char kType[kSizeType] = { 0x01 };
    const char kVersion[kSizeVersion] = { 0x01 };
    const char kTail[kSizeTail] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                     'p',  'a',  't',  'i',  'y',  'a'};

    bool isPatiya(const char stamp[kSizeStamp]) {
        for (unsigned int i = 0; i < kSizeStamp; ++i) {
            if (kStamp[i] != stamp[i])
                return false;
        }
        return true;
    }

    bool isConfig(const char type[kSizeType]) {
        for (unsigned int i = 0; i < kSizeType; ++i) {
            if (kType[i] != type[i])
                return false;
        }
        return true;
    }

    bool isVersion(const char version[kSizeVersion]) {
        for (unsigned int i = 0; i < kSizeVersion; ++i) {
            if (kVersion[i] != version[i])
                return false;
        }
        return true;
    }
}

namespace {
    const std::string  kEmptyStr("");
    const std::wstring kEmptyWStr(L"");
}

Config::Config(const std::wstring &filePath)
    : m_filePath(filePath)
    , m_loaded(false)
    , m_dirty(false)
{
}

Config::~Config()
{
    clear();
}

bool
Config::dirty() const
{
    return m_dirty;
}

bool
Config::load()
{    
    if (!m_filePath.empty() && !m_loaded)
    {
        clear();

        const std::ios_base::openmode mode 
            = (std::ios::in | std::ios::binary | std::ios::_Nocreate);
        std::fstream input(m_filePath.data(), mode);
                
        bool ok  = input.is_open();

        if (ok) { ok = loadHead(input); }
        if (ok) { ok = loadData(input); }
        if (ok) { ok = loadTail(input); }
        
        m_loaded = ok;
    }

    return m_loaded;
}

void
Config::save()
{    
    if (m_loaded && m_dirty)
    {
        const std::ios_base::openmode mode 
            = (std::ios::out | std::ios::binary); 
         // = (std::ios::out | std::ios::binary | std::ios::_Nocreate);
        std::fstream output(m_filePath.data(), mode);
        if (output.is_open())
        {
            saveHead(output);
            saveData(output);
            saveTail(output);
        
            output.close();

            m_dirty = false;
        }
    }
}

bool
Config::loadHead(std::fstream& in)
{
    char stamp[kSizeStamp];
    in.read(stamp, kSizeStamp);
    if (in.eof() || !::isPatiya(stamp))
        return false;

    char type[kSizeType];
    in.read(type, kSizeType);
    if (in.eof() || !::isConfig(type))
        return false;

    char version[kSizeVersion];
    in.read(version, kSizeVersion);
    if (in.eof() || !::isVersion(version))
        return false;

    return true;
}

bool
Config::loadData(std::fstream& in)
{
    for(;;)
    {
        // Load the length of name.
        //
        unsigned int lengthOfName = 0;
        if (!Util::loadLength(in, lengthOfName))
            return false;

        // Check if alreay reach the tail. If yes, go away from the loop.
        //
        if (0 == lengthOfName)
            break;

        // Load the name.
        //
        std::string name;
        if (!Util::loadName(in, lengthOfName, name))
            return false;

        // Load the type.
        //
        char type;
        in.read(&type, 1);
        if (in.eof())
            return false;

        // New a value according to the type.
        //
        Value* value = newValue(name, type);
        if (!value)
            return false;

        // If fail to load data, delete this new value.
        //
        if (!value->loadData(in))
        {
            delete value;
            value = 0;

            return false;
        }

        m_valueSet[name] = value;
    }

    return true;
}

bool
Config::loadTail(std::fstream& in)
{
    // Read the tail.
    //
    char bufTail[kSizeTail - kSizeLength];
    in.read(bufTail, kSizeTail - kSizeLength);
    if (in.eof())
        return false;

    // Read again one byte to check if alreay reach the end of file.
    //
    char byte;
    in.read(&byte, 1);
    if (!in.eof())
        return false;

    // Check the tail.
    //    
    for (int i = kSizeLength; i < kSizeTail; ++i) {
        if (kTail[i] != bufTail[i - kSizeLength])
            return false;
    }

    return true;
}

void
Config::saveHead(std::fstream& out)
{
    out.write(kStamp,    kSizeStamp);
    out.write(kType,     kSizeType);
    out.write(kVersion,  kSizeVersion);
}

void
Config::saveData(std::fstream& out)
{
    for (ValueSet::const_iterator it = m_valueSet.begin();
         it != m_valueSet.end(); ++it)
    {
        it->second->save(out);
    }
}

void
Config::saveTail(std::fstream& out)
{
    out.write(kTail, kSizeTail);
}

void
Config::clear()
{
    for (ValueSet::iterator it = m_valueSet.begin();
         it != m_valueSet.end(); ++it)
    {
        delete it->second;
        it->second = 0;
    }

    m_valueSet.clear();
}

bool
Config::setValue(const std::string& name,
                 const std::string& value)
{
    ValueSet::const_iterator it = m_valueSet.find(name);
    if (it == m_valueSet.end())
    {
        if (Value* aValue = newValue(name, Value::eStr))
        {
            StrValue* theValue = static_cast<StrValue*>(aValue);
            m_valueSet[name] = theValue;
            theValue->data(value);
            return true;
        }
        else
            assert(false);
    }
    else if (it->second->type() == Value::eStr)
    {
        StrValue* theValue = static_cast<StrValue*>(it->second);
        theValue->data(value);
        return true;
    }

    return false;
}

bool
Config::getValue(const std::string& name,
                       std::string& value)
{
    ValueSet::const_iterator it = m_valueSet.find(name);
    if (   it != m_valueSet.end()
        && it->second->type() == Value::eStr)
    {
        StrValue* theValue = static_cast<StrValue*>(it->second);
        value = theValue->data();
        return true;
    }

    return false;
}

bool
Config::setValue(const std::string& name,
                 const __int32 value)
{
    ValueSet::const_iterator it = m_valueSet.find(name);
    if (it == m_valueSet.end())
    {
        if (Value* aValue = newValue(name, Value::eInt32))
        {
            Int32Value* theValue = static_cast<Int32Value*>(aValue);
            m_valueSet[name] = theValue;
            theValue->data(value);
            return true;
        }
        else
            assert(false);
    }
    else if (it->second->type() == Value::eInt32)
    {
        Int32Value* theValue = static_cast<Int32Value*>(it->second);
        theValue->data(value);
        return true;
    }

    return false;
}

bool
Config::getValue(const std::string& name,
                 __int32& value)
{
    ValueSet::const_iterator it = m_valueSet.find(name);
    if (   it != m_valueSet.end()
        && it->second->type() == Value::eInt32)
    {
        Int32Value* theValue = static_cast<Int32Value*>(it->second);
        value = theValue->data();
        return true;
    }

    return false;
}

void
Config::onValueChanged()
{
    m_dirty = true;
}

const std::wstring&
Config::filePath() const
{
    return m_filePath;
}

Value*
Config::newValue(const std::string& name, const char type)
{
    switch(type)
    {
    case Value::eStr:       return (new StrValue(this, name));
    case Value::eInt32:     return (new Int32Value(this, name));
    };

    return 0;
}